<?php

use \org\copperforest\oak;

/**
 * Archivo que describe la clase Node
 * @author Alejandro Gama <alejandro.gama@gmail.com>
 * @version 1.0
 * @package core
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
 * @todo habría que implementar otra cosa si el usuario tuviese permisos para create procedure, temporary tables y vistas
 */
//EN LOS WHERE DONDE COMPARO UNOS VERTICES FIN Y OTROS INICIO, PONER EN LA WHERE EL VERTICE FIN PRIMERO
//YA QUE ES DE ESPERAR QUE HAYA SIEMPRE MENOS VERTICES FIN
//
//TENGO QUE COMPROBAR LOS RETORNOS
//@TODO TENGO QUE VER QUE HAGO CON LAS CONSULTAS UNBUFFERED!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//Ya que no puedo anidarlas...................................
//@TODO ALGO ESTA MAL QUE NO REHACE LOS DAGs
//todos los vertices deben ser indicados agrupados por tipo de vertice:
//array('vertexType'=>array('id11',array('id12','id22'))
//Otra mejora sería que las funciones estén preparadas para conjuntos de vertices
class Security
{

    private static $locked = 0;

    //roles es un array indexado por el id y en cada id las etiquetas
    static function addUserRoles( $user, $roles )
    {
        //tengo que comprobar si no existe
        //ahora $rol es un array con una etiqueta y un id

        $sql = '';
        foreach ( $roles as $t => $ids ) {
            foreach ( $ids as $id ) {
                if ( !empty( $sql ) ) $sql.=', ';

                $sql.='(' . $user . ',' . MyLink::quote( $t ) . ',' . (( int ) $id) . ')';
            }
        }


        if ( !empty( $sql ) ) {
            //esto puede dar problemas ante roles duplicados.....
            $sql = 'INSERT INTO security_roles (User,Rol,RolId) VALUES ' . $sql . ' ON DUPLICATE KEY UPDATE User=' . $user;
            MyLink::$default->execute( $sql );

            //si añadimos un rol basico a un usuario no tenemos que actualizar roles ya que el grupo actual es un subconjunto válido
            //SecurityCache::clear($roles);
        }
    }

    static function removeUserRoles( $user, $roles = null )
    {
        if ( !is_array( $roles ) || $roles == null ) {
            //el usuario puede estar logueado con un grupo con el subconjunto de roles, por lo que habría que eliminar todos los grupos que tengan
            //alguno de sus roles básicos
            $roles = MyLink::$default->get_rows( 'SELECT Rol,RolId FROM security_roles WHERE user=' . (( int ) $user) );


            $sql = 'DELETE FROM security_roles  WHERE user=' . (( int ) $user);
            MyLink::$default->execute( $sql );


            //que grupos actualizo??
        } else {
            $trozossql = array( );

            foreach ( $roles as $t => $ids )
                    $trozossql[ ] = 'RolId IN (' . implode( ',', $ids ) . ') AND Rol=' . MyLink::quote( $t );

            $sql = 'DELETE FROM security_roles WHERE ( ' . implode( ' OR ',
                            $trozossql ) . ' ) AND user=' . $user;
            MyLink::$default->execute( $sql );

            //Debería comprobar si quedan de esos roles y si no quedan eliminar los grupos
        }

        //@TODO
        //pero esto no tiene que elimanar los roles del grupo ... lo que tiene es que desactivar los grupos a los que
        //asociado el usuario en los que intervenga alguno de los roles
        //
        //Esto puede hacer que un usuario no pueda acceder a determinado grupo pero el grupo sigue siendo el mismo

        SecurityCache::clear( $roles, true ); //tengo que eliminar los grupos que tengan estos roles ya que el usuario puede estar utilizando uno
    }

    static function addRolEdges( $startVertices, $endVertex, $endVertexId = 0 )
    {
        //$startVertex debería ser un array con un único elemento con la clave con el tipo de vertice y como valor un array de un único elto
        //este elemento tendrá los ids del vertice en un array

        self::lock();

        self::addEdges( 'rol', $startVertices, $endVertex, $endVertexId );

        //@TODO
        //Aqui hay que actualizar los grupos pero no desactivarlos en los usuarios
        SecurityCache::clear( $startVertices, true, $endVertex, $endVertexId );

        self::unlock();
    }

    static function removeRolEdges( $startVertices, $endVertex, $endVertexId = 0 )
    {
        self::lock();

        self::removeEdges( $startVertices, $endVertex, $endVertexId );

        //@TODO
        //Esto es lo mas complicado ya que el si eliminamos el vertice que lleva a un usuario a un grupo deberiamos eliminarlo del grupo
        SecurityCache::clear( $startVertices, true, $endVertex, $endVertexId );

        self::unlock();
    }

    static function removeRoles( $vertices )
    {
        self::lock();

        $sqlinicio = '';
        foreach ( $vertices as $t => $ids ) {

            if ( !empty( $sql ) ) $sqlinicio.=' OR ';

            $sqlinicio.='VerticeInicioId IN (' . implode( ',', $ids ) . ') AND VerticeInicio=' . MyLink::quote( $t );
        }

        if ( SECURITY_USE_TABLES ) {

            if ( SECURITY_USE_TEMPORARY ) {
                $sql = 'CREATE TEMPORARY TABLE `borrar_aristas` (id INT);';
                MyLink::$default->execute( $sql );

                $sql = 'CREATE TEMPORARY TABLE `borrar_aristas_aux` (id INT)';
                MyLink::$default->execute( $sql );
            }


            $sql = 'INSERT INTO `borrar_aristas` (id) SELECT Id FROM security_edges WHERE ' . $sqlinicio;
            MyLink::$default->execute( $sql );

            while ( MyLink::$default->affected_rows != 0 ) {

                $sql = 'DELETE FROM `borrar_aristas_aux` WHERE 1';
                MyLink::$default->execute( $sql );

                $sql = 'INSERT INTO `borrar_aristas_aux` SELECT Id FROM security_edges WHERE IdAristaEntrada IN (SELECT id FROM `borrar_aristas`) OR IdAristaSalida IN (SELECT id FROM `borrar_aristas`)';
                MyLink::$default->execute( $sql );

                $sql = 'DELETE FROM `borrar_aristas_aux` WHERE id IN ( SELECT id FROM `borrar_aristas`)'; //elimino posibles duplicadas
                MyLink::$default->execute( $sql );

                $sql = 'INSERT INTO `borrar_aristas` SELECT id FROM `borrar_aristas_aux`';
                MyLink::$default->execute( $sql );
            }

            if ( SECURITY_USE_TEMPORARY ) {
                $sql = 'DROP TEMPORARY TABLE `borrar_aristas_aux`';
                MyLink::$default->execute( $sql );
                /*
                  $sql='CREATE TEMPORARY TABLE `borrar_privilegios` (id INT)';
                  MyLink::$default->execute($sql);

                  $sql='CREATE TEMPORARY TABLE `borrar_privilegios_aux` (id INT);';
                  MyLink::$default->execute($sql);
                 */
            }
            /*
              //borramos todos los privilegios asociados a esas aristas
              $sql='INSERT INTO `borrar_privilegios` (id) SELECT Id FROM security_privileges WHERE IdAristaRol IN (SELECT id FROM `borrar_aristas`) OR IdAristaRecurso IN (SELECT id FROM `borrar_aristas`)';
              MyLink::$default->execute($sql);


              while(MyLink::$default->affected_rows!==0){

              $sql='DELETE FROM `borrar_privilegios_aux` WHERE 1';
              MyLink::$default->execute($sql);

              $sql='INSERT INTO `borrar_privilegios_aux` (id) SELECT Id FROM security_privileges WHERE IdPrivilegioCausante IN (SELECT id FROM `borrar_privilegios`)';
              MyLink::$default->execute($sql);

              $sql='DELETE FROM `borrar_privilegios_aux` WHERE id IN ( SELECT id FROM `borrar_privilegios`)';
              MyLink::$default->execute($sql);

              $sql='INSERT INTO `borrar_privilegios` (id) SELECT aux.id FROM `borrar_privilegios_aux` as aux';
              MyLink::$default->execute($sql);

              }

             */
            //Ya puedo borrar las aristas y la tabla
            $sql = 'DELETE FROM security_edges WHERE Id IN (SELECT id FROM `borrar_aristas`);';
            MyLink::$default->execute( $sql );

            if ( SECURITY_USE_TEMPORARY ) {
                $sql = 'DROP TEMPORARY TABLE `borrar_aristas`;';
                MyLink::$default->execute( $sql );
            } else {
                $sql = 'DELETE FROM `borrar_aristas` WHERE 1';
                MyLink::$default->execute( $sql );
            }

            /*
              $sql='DELETE FROM security_privileges WHERE Id IN (SELECT id FROM `borrar_privilegios`)';
              MyLink::$default->execute($sql);

              if(SECURITY_USE_TEMPORARY){
              $sql='DROP TEMPORARY TABLE `borrar_privilegios`;';
              MyLink::$default->execute($sql);

              $sql='DROP TEMPORARY TABLE `borrar_privilegios_aux`;';
              MyLink::$default->execute($sql);
              }
              else{
              $sql='DELETE FROM `borrar_privilegios` WHERE 1';
              MyLink::$default->execute($sql);
              }
             */
        } else {
            $idstring = '';
            $aristas = MyLink::$default->get_rows( 'SELECT Id FROM security_edges WHERE ' . $sqlinicio,
                            true )->toArray();

            if ( !empty( $aristas ) ) {
                $adelante = true;

                while ( $adelante ) {
                    $adelante = false;

                    $idstring = implode( ',', $aristas );

                    $heredadas = MyLink::$default->get_rows( 'SELECT Id FROM security_edges WHERE IdAristaEntrada IN (' . $idstring . ') OR IdAristaSalida IN (' . $idstring . ')' );

                    foreach ( $heredadas as $h ) {
                        if ( !in_array( $h[ 0 ], $aristas ) ) {
                            $aristas[ ] = $h[ 0 ];
                            $adelante = true;
                        }
                    }
                }

                unset( $aristas );

                //Ya puedo borrar las aristas y la tabla
                $sql = 'DELETE FROM security_edges WHERE Id IN (' . $idstring . ');';
                MyLink::$default->execute( $sql );
            }
            //$idstring=implode(',',$aristas);
            /*
              //borramos todos los privilegios asociados a esas aristas
              $privilegios=array();
              $nuevas=MyLink::$default->get_rows('SELECT Id FROM security_privileges WHERE IdAristaRol IN ('.$idstring.') OR IdAristaRecurso IN ('.$idstring.')',true)->toArray();



              while(!empty($nuevas)){
              $privilegios=array_merge($nuevas,$privilegios);

              $nuevas=MyLink::$default->get_rows('SELECT Id FROM security_edges WHERE  IdPrivilegioCausante IN ('.implode(',',$privilegios).')',true)->toArray();
              $nuevas=array_diff($nuevas,$privilegios);
              }

              $sql='DELETE FROM security_privileges WHERE Id IN ('.implode(',',$privilegios).');';
              MyLink::$default->execute($sql);
              unset($privilegios);
             */
        }

//AQUI DEBERÍA BORRAR TODOS LOS GRUPOS QUE TENGAN LOS ROLES BORRADOS
        //Aqui no tengo que actualizar la cache de seguridad???
        SecurityCache::clear( $vertices, true );

        self::unlock();

        return true;
    }

    static function addResourceEdges( $startVertices, $endVertex,
            $endVertexId = 0 )
    {
        self::lock();


        //tengo que borrar a mayores aquellos que
        $retorno = self::addEdges( 'resource', $startVertices, $endVertex,
                        $endVertexId );

        if ( $retorno ) {

            $roles = MyLink::$default->get_rows( 'SELECT RolId, Rol FROM security_privileges WHERE Recurso=' . MyLink::quote( $endVertex ) . ' AND RecursoId=' . (( int ) $endVertexId),
                            true )->toArray();

            $clear = array( );
            foreach ( $roles as $r ) {
                if ( empty( $clear[ $r[ 1 ] ] ) ) $clear[ $r[ 1 ] ] = array( );

                $clear[ $r[ 1 ] ][ ] = $r[ 0 ];
            }

            SecurityCache::clear( $clear );
        }

        self::unlock();

        return $retorno;
    }

    static function removeResourceEdges( $startVertices, $endVertexType,
            $endVertexId = 0 )
    {
        self::lock();

        $roles = MyLink::$default->get_rows( 'SELECT RolId,Rol FROM security_privileges WHERE Recurso=' . MyLink::quote( $endVertexType ) . ' AND RecursoId=' . (( int ) $endVertexId),
                        true )->toArray();
        $retorno = self::removeEdges( $startVertices, $endVertexType,
                        $endVertexId );

        //si esto implicara la desaparición de un recurso de la tabla de security_edges debería implicar la desparición de los privilegios para ese recurso
        if ( $retorno ) {
            $clear = array( );
            foreach ( $roles as $r ) {
                if ( empty( $clear[ $r[ 1 ] ] ) ) $clear[ $r[ 1 ] ] = array( );

                $clear[ $r[ 1 ] ][ ] = $r[ 0 ];
            }

            SecurityCache::clear( $clear );
        }

        self::unlock();

        return $retorno;
    }

    static function removeResources( $vertices )
    {
        if ( empty( $vertices ) ) return false;

        self::lock();


        $sqlrecurso = '';
        $sqlvertice = '';
        foreach ( $vertices as $t => $ids ) {
            if ( !empty( $sqlrecurso ) ) {
                $sqlrecurso.=' OR ';
                $sqlvertice.=' OR ';
            }

            $sqlString = implode( ',', $ids );
            $sqlrecurso.='RecursoId IN (' . $sqlString . ') AND Recurso=' . MyLink::quote( $t );
            $sqlvertice.='VerticeInicioId IN (' . $sqlString . ') AND VerticeInicio=' . MyLink::quote( $t );
        }


        $roles = MyLink::$default->get_rows( 'SELECT RolId, Rol FROM security_privileges WHERE ' . $sqlrecurso,
                        true )->toArray();

        if ( SECURITY_USE_TABLES ) {


            if ( SECURITY_USE_TEMPORARY ) {
                $sql = 'CREATE TEMPORARY TABLE `borrar_aristas` (id INT);';
                MyLink::$default->execute( $sql );

                $sql = 'CREATE TEMPORARY TABLE `borrar_aristas_aux` (id INT)';
                MyLink::$default->execute( $sql );
            }

            $sql = 'INSERT INTO `borrar_aristas` (id) SELECT Id FROM security_edges WHERE ' . $sqlvertice;
            MyLink::$default->execute( $sql );

            //estas acciones de insercion y
            while ( MyLink::$default->affected_rows != 0 ) {
                $sql = 'DELETE FROM `borrar_aristas_aux` WHERE 1';
                MyLink::$default->execute( $sql );

                $sql = 'INSERT INTO `borrar_aristas_aux` SELECT Id FROM security_edges WHERE IdAristaEntrada IN (SELECT id FROM `borrar_aristas`) OR IdAristaSalida IN (SELECT id FROM `borrar_aristas`)';
                MyLink::$default->execute( $sql );

                $sql = 'DELETE FROM `borrar_aristas_aux` WHERE id IN ( SELECT id FROM `borrar_aristas`)';
                MyLink::$default->execute( $sql );

                $sql = 'INSERT INTO `borrar_aristas` SELECT aux.id FROM `borrar_aristas_aux` as aux';
                MyLink::$default->execute( $sql );
            }

            if ( SECURITY_USE_TEMPORARY ) {
                $sql = 'DROP TEMPORARY TABLE `borrar_aristas_aux`';
                MyLink::$default->execute( $sql );
                /*
                  $sql='CREATE TEMPORARY TABLE `borrar_privilegios` (id INT)';
                  MyLink::$default->execute($sql);
                 */
            }
            /*
              //borramos todos los privilegios asociados a esas aristas
              $sql='INSERT INTO `borrar_privilegios` (id) SELECT Id FROM security_privileges WHERE IdAristaRol IN (SELECT id FROM `borrar_aristas`) OR IdAristaRecurso IN (SELECT id FROM `borrar_aristas`) OR '.$sqlrecurso;
              MyLink::$default->execute($sql);


              if(SECURITY_USE_TEMPORARY){
              $sql='CREATE TEMPORARY TABLE `borrar_privilegios_aux` (id INT);';
              MyLink::$default->execute($sql);
              }

              while(MyLink::$default->affected_rows!==0){

              $sql='DELETE FROM `borrar_privilegios_aux` WHERE 1';
              MyLink::$default->execute($sql);

              $sql='INSERT INTO `borrar_privilegios_aux` (id) SELECT Id FROM security_privileges WHERE IdPrivilegioCausante IN (SELECT id FROM `borrar_privilegios`)';
              MyLink::$default->execute($sql);

              $sql='DELETE FROM `borrar_privilegios_aux` WHERE id IN ( SELECT id FROM `borrar_privilegios`)';
              MyLink::$default->execute($sql);

              $sql='INSERT INTO `borrar_privilegios` (id) SELECT DISTINCT id FROM `borrar_privilegios_aux`';
              MyLink::$default->execute($sql);
              }

              $sql='DELETE FROM security_privileges WHERE Id IN (SELECT id FROM `borrar_privilegios`)';
              MyLink::$default->execute($sql);
             */

            //Ya puedo borrar las aristas y la tabla
            $sql = 'DELETE FROM security_edges WHERE Id IN (SELECT id FROM `borrar_aristas`);';
            MyLink::$default->execute( $sql );


            if ( SECURITY_USE_TEMPORARY ) {
                $sql = 'DROP TEMPORARY TABLE `borrar_aristas`;';
                MyLink::$default->execute( $sql );
                /*
                  $sql='DROP TEMPORARY TABLE `borrar_privilegios`;';
                  MyLink::$default->execute($sql);

                  $sql='DROP TEMPORARY TABLE `borrar_privilegios_aux`;';
                  MyLink::$default->execute($sql);
                 */
            } else {
                $sql = 'DELETE FROM `borrar_aristas` WHERE 1';
                MyLink::$default->execute( $sql );

                //$sql='DELETE FROM `borrar_privilegios` WHERE 1';
                //MyLink::$default->execute($sql);
            }
        } else {



            $idstring = '';
            $aristas = MyLink::$default->get_rows( 'SELECT Id FROM security_edges WHERE ' . $sqlvertice,
                            true )->toArray();

            if ( !empty( $aristas ) ) {
                $adelante = true;

                while ( $adelante ) {
                    $adelante = false;

                    $idstring = implode( ',', $aristas );

                    $heredadas = MyLink::$default->get_rows( 'SELECT Id FROM security_edges WHERE IdAristaEntrada IN (' . $idstring . ') OR IdAristaSalida IN (' . $idstring . ')' );

                    //$heredadas=array_diff($heredadas,$aristas);
                    //$aristas=array_merge($aristas,$heredadas);

                    foreach ( $heredadas as $h ) {
                        if ( !in_array( $h[ 0 ], $aristas ) ) {
                            $aristas[ ] = $h[ 0 ];
                            $adelante = true;
                        }
                    }
                }

                unset( $aristas );

                //Ya puedo borrar las aristas y la tabla
                $sql = 'DELETE FROM security_edges WHERE Id IN (' . $idstring . ');';
                MyLink::$default->execute( $sql );
            }


            /*
              //$idstring=implode(',',$aristas);

              //borramos todos los privilegios asociados a esas aristas
              $privilegios=array();
              $nuevas=MyLink::$default->get_rows('SELECT Id FROM security_privileges WHERE IdAristaRol IN ('.$idstring.') OR IdAristaRecurso IN ('.$idstring.') OR '.$sqlrecurso, true)->toArray();



              while(!empty($nuevas)){
              $privilegios=array_merge($nuevas,$privilegios);

              $nuevas=MyLink::$default->get_rows('SELECT Id FROM security_edges WHERE  IdPrivilegioCausante IN ('.implode(',',$privilegios).')',true)->toArray();
              $nuevas=array_diff($nuevas,$privilegios);
              }

              $sql='DELETE FROM security_privileges WHERE Id IN ('.implode(',',$privilegios).');';
              MyLink::$default->execute($sql);
              unset($privilegios);
             */
        }


        $clear = array( );
        foreach ( $roles as $r ) {
            if ( empty( $clear[ $r[ 1 ] ] ) ) $clear[ $r[ 1 ] ] = array( );

            $clear[ $r[ 1 ] ][ ] = $r[ 0 ];
        }

        SecurityCache::clear( $clear );

        self::unlock();

        return true;
    }

    static function addPrivileges( $rolType, $rolId, $resourceType, $resourceId,
            $classes )
    {
        self::lock();

        foreach ( $classes as $c )
                self::addPrivilege( $rolType, $rolId, $resourceType,
                    $resourceId, $c );

        self::unlock();
    }

    //LA TABLA DE Privileges Tendría que ser id, rol , resource, IdAristaHerencia,  IdPrivilegioCausante (este puede ser el original o el que sucede por causa de una herencia del original)
    static function addPrivilege( $rolType, $rolId, $resourceType, $resourceId,
            $class )
    {
        self::lock();

        $rolId = ( int ) $rolId;
        $resourceId = ( int ) $resourceId;

        $sRolType = MyLink::quote( $rolType );
        $sResourceType = MyLink::quote( $resourceType );

        $sClass = MyLink::quote( $class );

        $res = MyLink::$default->get_rows( 'SELECT id FROM security_privileges WHERE Rol=' . $sRolType . ' AND RolId=' . $rolId . ' AND Recurso=' . $sResourceType . ' AND RecursoId=' . $resourceId . ' AND Clase=' . $sClass );
        //$res = MyLink::$default->get_rows('SELECT id FROM security_privileges WHERE Rol=' . $sRolType . ' AND RolId=' . $rolId . ' AND Recurso=' . $sResourceType . ' AND RecursoId=' . $resourceId . ' AND Clase=' . $sClass . ' AND implicita=\'N\'');
        if ( $res->numRows() > 0 ) {
            self::unlock();
            return false;
        }

        $sql = 'INSERT INTO security_privileges (Rol,RolId,Recurso,RecursoId,Clase) VALUES (' . $sRolType . ',' . $rolId . ',' . $sResourceType . ',' . $resourceId . ',' . $sClass . ')';
        MyLink::$default->execute( $sql );
        $idNuevo = MyLink::$default->insert_id;

        // $sql = 'UPDATE security_privileges SET IdPrivilegioCausante=' . $idNuevo . ' WHERE Id=' . $idNuevo;
        //MyLink::$default->execute($sql);

        /*
          //tengo que añadir al rol privilegios sobre todos los hijos del recurso
          $sql='INSERT INTO security_privileges (Rol, RolId, Recurso, RecursoId, Clase, IdAristaRecurso, IdPrivilegioCausante, Implicita) ';
          $sql.='SELECT '.$sRolType.','.$rolId.', VerticeInicio, VerticeInicioId, '.$sClass.', Id, '.$idNuevo.', \'S\' FROM security_edges WHERE VerticeFin='.$sResourceType.' AND VerticeFinId='.$resourceId;
          MyLink::$default->execute($sql);

          // y a los hijos de rol privilegios sobre el recurso
          $sql='INSERT INTO security_privileges (Rol, RolId, Recurso, RecursoId, Clase, IdAristaRol, IdPrivilegioCausante, Implicita) ';
          $sql.='SELECT VerticeInicio, VerticeInicioId, '.$sResourceType.','.$resourceId.', '.$sClass.', Id, '.$idNuevo.', \'S\' FROM security_edges WHERE VerticeFin='.$sRolType.' AND VerticeFinId='.$rolId;
          MyLink::$default->execute($sql);

          //ahora calculo el producto cartesiano entre las inferiores al rol (con verticeFin el rol) y las inferiores al recurso (con verticeFin l recurso)
          //NO HAY NINGÚN PRIVILEGIO CREADO CON ESTE INSERT ¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿???????????????????
          $sql='INSERT INTO security_privileges (Rol,RolId,Recurso,RecursoId,Clase,IdAristaRol,IdAristaRecurso,IdPrivilegioCausante,Implicita) ';
          $sql.='SELECT A.VerticeInicio, A.VerticeInicioId, B.VerticeInicio,B.VerticeInicioId,'.$sClass.', A.Id, B.Id,'.$idNuevo.', \'S\' ' ;
          $sql.='FROM security_edges as A, security_edges as B WHERE A.VerticeFin='.$sRolType.' AND A.VerticeFinId='.$rolId.' AND B.VerticeFin='.$sResourceType.' AND B.VerticeFinId='.$resourceId;
          MyLink::$default->execute($sql);
         */
        SecurityCache::clear( array( $rolType => array( $rolId ) ) );

        self::unlock();

        return $idNuevo;
    }

    static function removePrivileges( $rolType, $rolId, $resourceType,
            $resourceId, $classes )
    {
        self::removePrivilege( $rolType, $rolId, $resourceType, $resourceId,
                $classes );
    }

    static function removePrivilege( $rolType, $rolId, $resourceType,
            $resourceId, $class )
    {
        if ( !is_array( $class ) ) $class = array( $class );

        $idPrivilegios = MyLink::$default->get_rows( 'SELECT Id FROM security_privileges WHERE Rol=' . MyLink::quote( $rolType ) . ' AND RolId=' . (( int ) $rolId) . ' AND Recurso=' . MyLink::quote( $resourceType ) . ' AND RecursoId=' . (( int ) $resourceId) . ' AND Clase=' . MyLink::quote( $class ) . ' LIMIT 1',
                        true )->toArray();

        if ( count( $idPrivilegios ) != count( $class ) ) return false;

        self::lock();

        /*
          if(SECURITY_USE_TABLES){

          if(SECURITY_USE_TEMPORARY){
          $sql='CREATE TEMPORARY TABLE `borrar_privilegios` (id INT);';
          MyLink::$default->execute($sql);

          $sql='CREATE TEMPORARY TABLE `borrar_privilegios_aux` (id INT)';
          MyLink::$default->execute($sql);
          }

          $sql='INSERT INTO `borrar_privilegios` (id) VALUES ('.implode('),(',$idPrivilegios).')';
          MyLink::$default->execute($sql);


          while(MyLink::$default->affected_rows!=0){
          //si no se ha insertado ninguna que valor me daría en el insert_id
          $sql='DELETE FROM `borrar_privilegios_aux` WHERE 1';
          MyLink::$default->execute($sql);

          $sql='INSERT INTO `borrar_privilegios_aux` (id) SELECT Id FROM security_privileges WHERE IdPrivilegioCausante IN (SELECT id FROM `borrar_privilegios`)';
          MyLink::$default->execute($sql);

          $sql='DELETE FROM `borrar_privilegios_aux` WHERE id IN ( SELECT id FROM `borrar_privilegios`)';
          MyLink::$default->execute($sql);

          $sql='INSERT INTO `borrar_privilegios` (id) SELECT DISTINCT id FROM `borrar_privilegios_aux`';
          MyLink::$default->execute($sql);
          }

          $sql='DELETE FROM security_privileges WHERE Id IN (SELECT id FROM `borrar_privilegios`);';
          MyLink::$default->execute($sql);

          if(SECURITY_USE_TEMPORARY){
          $sql='DROP TEMPORARY TABLE `borrar_privilegios`;';
          MyLink::$default->execute($sql);

          $sql='DROP TEMPORARY TABLE `borrar_privilegios_aux`';
          MyLink::$default->execute($sql);
          }
          else{
          $sql='DELETE FROM `borrar_privilegios` WHERE 1;';
          MyLink::$default->execute($sql);
          }
          }
          else{
          $privilegios=array();
          $nuevas=$idPrivilegios;

          while(!empty($nuevas)){
          $privilegios=array_merge($nuevas,$privilegios);

          $nuevas=MyLink::$default->get_rows('SELECT Id FROM security_edges WHERE  IdPrivilegioCausante IN ('.implode(',',$privilegios).')',true)->toArray();
          $nuevas=array_diff($nuevas,$privilegios);
          }

          $sql='DELETE FROM security_privileges WHERE Id IN ('.implode(',',$privilegios).');';
          MyLink::$default->execute($sql);
          unset($privilegios);
          }
         */
        $sql = 'DELETE FROM security_privileges WHERE Id IN (' . implode( ',',
                        $idPrivilegios ) . ');';
        MyLink::$default->execute( $sql );

        SecurityCache::clear( array( $rolType => array( $rolId ) ) );
        self::unlock();

        return true;
    }

    //Las funciones privadas no necesitan LOCK
    private static function addEdges( $scope, $startVertices, $endVertex,
            $endVertexId )
    {
        $sEndVertex = MyLink::quote( $endVertex );
        $endVertexId = ( int ) $endVertexId;
        $sqlbase = ',' . $sEndVertex . ',' . $endVertexId . ',' . MyLink::quote( $scope );

        $sqlinicio = '';
        $sqlfin = '';
        foreach ( $startVertices as $t => $ids ) {

            $ids = array_unique( $ids );

            if ( $t == $endVertex && in_array( $endVertexId, $ids ) )
                    return false;


            if ( !empty( $sqlinicio ) ) {
                $sqlinicio.=' OR ';
                $sqlfin.=' OR ';
            }

            $sqlString = implode( ',', $ids );

            $sqlinicio.='VerticeInicioId IN (' . $sqlString . ') AND VerticeInicio=' . MyLink::quote( $t );
            $sqlfin.='VerticeFinId IN (' . $sqlString . ') AND VerticeFin=' . MyLink::quote( $t );
        }


        //en la segunda condicion no hay condicion de implicita pq lo que se quiere evitar es la circularidad del grafo
        $sigo = MyLink::$default->get_rows(
                        'SELECT 1 FROM security_edges WHERE VerticeFin=' . $sEndVertex . ' AND VerticeFinId=' . $endVertexId . ' AND (' . $sqlinicio . ')  AND Implicita=\'N\' OR (' . $sqlfin . ') AND VerticeInicio=' . $sEndVertex . ' AND VerticeInicioId=' . $endVertexId . ' LIMIT 1'
                        , true )->getField( 0 );

        if ( !empty( $sigo ) ) return false;


        //en teoría esta sentencia si está en 'repeteable read' utilizará el autoindex??
        //@TODO: Aqui tendría que hacer una linea por cada startVertex
        $sqlinsert = '';
        foreach ( $startVertices as $t => $ids ) {

            foreach ( $ids as $id ) {
                if ( !empty( $sqlinsert ) ) $sqlinsert.=', ';

                $sqlinsert.='(' . (( int ) $id) . ',' . MyLink::quote( $t ) . ',\'N\'' . $sqlbase . ')';
            }
        }

        $sql = 'INSERT INTO security_edges (VerticeInicioId,VerticeInicio,Implicita,VerticeFin,VerticeFinId,Contexto) VALUES ' . $sqlinsert;
        MyLink::$default->execute( $sql );

        $idNueva = MyLink::$default->insert_id;
        $idUltima = MyLink::$default->affected_rows + MyLink::$default->insert_id - 1;

        //$sql='UPDATE security_edges SET IdAristaEntrada='.$idNueva.', IdAristaSalida='.$idNueva.', IdAristaCausante='.$idNueva.' WHERE Id='.$idNueva;
        //tendría que comprobar si esto sirve para algo
        $sql = 'UPDATE security_edges SET IdAristaEntrada=Id, IdAristaSalida=Id WHERE Id BETWEEN ' . $idNueva . ' AND ' . $idUltima;
        MyLink::$default->execute( $sql );

        //ahora conecto todos los hijos del startvertex con endVertex
        //para cada uno de los vertices de inicio su ID
        //@TODO TENGO QUE HACER UN INSERT DE ESTOS POR CADA startVertex
        $currentId = $idNueva;

        foreach ( $startVertices as $t => $ids ) {

            foreach ( $ids as $id ) {
                //esto tend´ria que ir subiendo idNueva
                $sql = 'INSERT INTO security_edges ( IdAristaEntrada, IdAristaSalida, VerticeInicio, VerticeInicioId, Implicita, VerticeFin, VerticeFinId, Contexto) ';
                $sql.='SELECT Id, ' . $currentId . ', VerticeInicio, VerticeInicioId, \'S\'' . $sqlbase . ' FROM security_edges WHERE VerticeFinId=' . (( int ) $id) . ' AND VerticeFin=' . MyLink::quote( $t );
                MyLink::$default->execute( $sql );

                $currentId++;

                if ( MyLink::$default->affected_rows > 0 )
                        $idUltima = MyLink::$default->affected_rows + MyLink::$default->insert_id - 1;
            }
        }

        //ahora que tengo todos los hijos de startVertex heredo los privilegios de endVertex
        //para ello hago un producto cartesiano entre los vertices que acabo de crear y los privilegios del endVertex
        /*
          $sql='INSERT INTO security_privileges (Clase, Rol, RolId, Recurso, RecursoId, IdPrivilegioCausante, IdAristaRol, Implicita)';
          if($scope==='rol'){
          $sql.=' SELECT A.Clase, B.VerticeInicio, B.VerticeInicioId, A.Recurso, A.RecursoId, A.Id,  B.Id, \'S\' FROM security_privileges as A, security_edges as B';
          $sql.=' WHERE B.Id BETWEEN '.$idNueva.' AND '.$idUltima.' AND A.RolId='.$endVertexId.' AND A.Rol='.$sEndVertex;
          }
          else{
          $sql.=' SELECT A.Clase, A.Rol, A.RolId, B.VerticeInicio, B.VerticeInicioId, A.Id,  B.Id, \'S\' FROM security_privileges as A, security_edges as B';
          $sql.=' WHERE B.Id BETWEEN '.$idNueva.' AND '.$idUltima.' AND A.RecursoId='.$endVertexId.' AND A.Recurso='.$sEndVertex;
          }
          MyLink::$default->execute($sql);
         */

        //ahora tengo que hacer un producto cartesiano entre las aristas creadas y las aristas que salen de $endVertex
        $sql = 'INSERT INTO security_edges (IdAristaEntrada, IdAristaSalida, VerticeInicio,VerticeInicioId, VerticeFin, VerticeFinId, Implicita, Contexto)';
        $sql.=' SELECT B.Id, A.Id, B.VerticeInicio, B.VerticeInicioId, A.VerticeFin, A.VerticeFinId, \'S\', ' . MyLink::quote( $scope ) . ' FROM security_edges AS A, security_edges AS B';
        $sql.=' WHERE B.Id BETWEEN ' . $idNueva . ' AND ' . $idUltima . ' AND A.VerticeInicioId=' . $endVertexId . ' AND A.VerticeInicio=' . $sEndVertex;
        MyLink::$default->execute( $sql );

        return $idNueva;
    }

    private static function removeEdges( $startVertices, $endVertex,
            $endVertexId = 0 )
    {

        //tengo que comprobar que no son vertices iguales y estan conectados
        $sqlinicio = '';
        foreach ( $startVertices as $t => $ids ) {

            $ids = array_unique( $ids );

            if ( $t == $endVertex && in_array( $endVertexId, $ids ) )
                    return false;


            if ( !empty( $sqlinicio ) ) $sqlinicio.=' OR ';

            $sqlinicio.='VerticeInicioId IN (' . implode( ',', $ids ) . ') AND VerticeInicio=' . MyLink::quote( $t );
        }



        //$sql='SELECT Id INTO @IdArista FROM security_edges WHERE VerticeInicio='.MyLink::quote($startVertex).' AND VerticeFin='.MyLink::quote($endVertex).' AND Implicita=\'N\'';
        //MyLink::$default->execute($sql);
        //$idArista=MyLink::$default->get_rows('SELECT @IdArista')->getField(0);

        $idAristas = MyLink::$default->get_rows( 'SELECT Id FROM security_edges WHERE VerticeFin=' . MyLink::quote( $endVertex ) . ' AND VerticeFinId=' . (( int ) $endVertexId) . ' AND (' . $sqlinicio . ') AND Implicita=\'N\'',
                        true )->toArray();
        if ( empty( $idAristas ) ) return false;

        if ( SECURITY_USE_TABLES ) {

            if ( SECURITY_USE_TEMPORARY ) {
                $sql = 'CREATE TEMPORARY TABLE `borrar_aristas` (id INT);';
                MyLink::$default->execute( $sql );

                $sql = 'CREATE TEMPORARY TABLE `borrar_aristas_aux` (id INT)';
                MyLink::$default->execute( $sql );
            }

            $sql = 'INSERT INTO `borrar_aristas` (id) VALUES (' . implode( '),(',
                            $idAristas ) . ')';
            MyLink::$default->execute( $sql );


            while ( MyLink::$default->affected_rows != 0 ) {
                $sql = 'DELETE FROM `borrar_aristas_aux` WHERE 1';
                MyLink::$default->execute( $sql );

                $sql = 'INSERT INTO `borrar_aristas_aux` SELECT Id FROM security_edges WHERE IdAristaEntrada IN (SELECT id FROM `borrar_aristas`) OR  IdAristaSalida IN (SELECT id FROM `borrar_aristas`)';
                MyLink::$default->execute( $sql );

                $sql = 'DELETE FROM `borrar_aristas_aux` WHERE id IN ( SELECT id FROM `borrar_aristas`)';
                MyLink::$default->execute( $sql );

                $sql = 'INSERT INTO `borrar_aristas` SELECT DISTINCT aux.id FROM `borrar_aristas_aux` as aux';
                MyLink::$default->execute( $sql );
            }

            if ( SECURITY_USE_TEMPORARY ) {
                $sql = 'DROP TEMPORARY TABLE `borrar_aristas_aux`';
                MyLink::$default->execute( $sql );

                //$sql='CREATE TEMPORARY TABLE `borrar_privilegios` (id INT)';
                //MyLink::$default->execute($sql);
            }
            /*
              //borramos todos los privilegios asociados a esas aristas
              //$sql='INSERT INTO `borrar_privilegios` (id) SELECT Id FROM security_privileges WHERE IdAristaRol IN (SELECT id FROM `borrar_aristas`) OR IdAristaRecurso IN (SELECT id FROM `borrar_aristas`)';
              $sql='INSERT INTO `borrar_privilegios` (id) SELECT Id FROM security_privileges WHERE IdAristaRol IN (SELECT id FROM `borrar_aristas`)';
              MyLink::$default->execute($sql);
              $sql='INSERT INTO `borrar_privilegios` (id) SELECT Id FROM security_privileges WHERE IdAristaRecurso IN (SELECT id FROM `borrar_aristas`)';
              MyLink::$default->execute($sql);
              //pero claro... estos pueden ser privilegios causantes de otros

             */

            if ( SECURITY_USE_TEMPORARY ) {



                //			$sql='CREATE TEMPORARY TABLE `borrar_privilegios_aux` (id INT);';
                //			MyLink::$default->execute($sql);
            }

            /*
              while(MyLink::$default->affected_rows!=0){

              $sql='DELETE FROM `borrar_privilegios_aux` WHERE 1';
              MyLink::$default->execute($sql);

              $sql='INSERT INTO `borrar_privilegios_aux` (id) SELECT Id FROM security_privileges WHERE IdPrivilegioCausante IN (SELECT id FROM `borrar_privilegios`)';
              MyLink::$default->execute($sql);

              $sql='DELETE FROM `borrar_privilegios_aux` WHERE id IN ( SELECT id FROM `borrar_privilegios`)';
              MyLink::$default->execute($sql);

              $sql='INSERT INTO `borrar_privilegios` (id) SELECT DISTINCT aux.id FROM `borrar_privilegios_aux` as aux';
              MyLink::$default->execute($sql);
              }
             */
            $sql = 'DELETE FROM security_edges WHERE Id IN (SELECT id FROM `borrar_aristas`);';
            MyLink::$default->execute( $sql );

            if ( SECURITY_USE_TEMPORARY ) {
                $sql = 'DROP TEMPORARY TABLE `borrar_aristas`;';
                MyLink::$default->execute( $sql );


                //			$sql='CREATE TEMPORARY TABLE `borrar_privilegios_aux` (id INT);';
                //			MyLink::$default->execute($sql);
            } else {
                $sql = 'DELETE FROM `borrar_aristas` WHERE 1';
                MyLink::$default->execute( $sql );
            }
            /*
              $sql='DELETE FROM security_privileges WHERE Id IN (SELECT id FROM `borrar_privilegios`)';
              MyLink::$default->execute($sql);


              if(SECURITY_USE_TEMPORARY){
              $sql='DROP TEMPORARY TABLE `borrar_privilegios`;';
              MyLink::$default->execute($sql);

              $sql='DROP TEMPORARY TABLE `borrar_privilegios_aux`;';
              MyLink::$default->execute($sql);
              }
              else{
              $sql='DELETE FROM `borrar_privilegios` WHERE 1;';
              MyLink::$default->execute($sql);
              }
             */
        } else {

            $idstring = '';
            $aristas = $idAristas;
            $adelante = true;


            while ( !empty( $adelante ) ) {
                $adelante = false;
                $idstring = implode( ',', $aristas );

                $heredadas = MyLink::$default->get_rows( 'SELECT Id FROM security_edges WHERE IdAristaEntrada IN (' . $idstring . ') OR IdAristaSalida IN (' . $idstring . ')' );

                foreach ( $heredadas as $h ) {
                    if ( !in_array( $h[ 0 ], $aristas ) ) {
                        $aristas[ ] = $h[ 0 ];
                        $adelante = true;
                    }
                }
            }

            unset( $aristas );

            //Ya puedo borrar las aristas y la tabla
            $sql = 'DELETE FROM security_edges WHERE Id IN (' . $idstring . ');';
            MyLink::$default->execute( $sql );
        }

        return true;
    }

    static public function rebuildDAGs()
    {
        self::lock();

        $aristas = MyLink::$default->get_rows( 'SELECT Contexto, VerticeInicioId, VerticeInicio, VerticeFin, VerticeFinId FROM security_edges WHERE implicita=\'N\'' );

        //Borro TODAS LAS ARISTAS
        $sql = 'DELETE FROM security_edges WHERE 1';
        MyLink::$default->execute( $sql );
        /*
          $sql='SELECT Rol, Recurso FROM security_privileges WHERE Implicita=\'N\'';
          $privilegios=MyLink::$default->get_rows($sql);
         */
        $sql = 'DELETE FROM security_privileges WHERE Implicita=\'S\'';
        MyLink::$default->execute( $sql );


        //@TODO: ESTO
        //TENGO QUE AGRUPARLOS POR EL TIPO DE Vertice
        //primero tengo que agruparlas por vertice fin
        $rolesFin = array( );
        $resourcesFin = array( );

        foreach ( $aristas as $a ) {
            if ( $a[ 0 ] === 'resource' ) $verticesFin = &$resourcesFin;
            else $verticesFin = & $rolesFin;

            if ( !isset( $verticesFin[ $a[ 3 ] ] ) )
                    $verticesFin[ $a[ 3 ] ] = array( );

            if ( !isset( $verticesFin[ $a[ 3 ] ][ $a[ 4 ] ] ) )
                    $verticesFin[ $a[ 3 ] ][ $a[ 4 ] ] = array( ); //este es el array de vertices Inicio

            if ( !isset( $verticesFin[ $a[ 3 ] ][ $a[ 4 ] ][ $a[ 2 ] ] ) )
                    $verticesFin[ $a[ 3 ] ][ $a[ 4 ] ][ $a[ 2 ] ] = array( );

            $verticesFin[ $a[ 3 ] ][ $a[ 4 ] ][ $a[ 2 ] ][ ] = $a[ 1 ];
        }

        foreach ( $resourcesFin as $type => $struct ) {
            foreach ( $struct as $id => $startVertices )
                    self::addResourceEdges( $startVertices, $type, $id );
        }
        foreach ( $rolesFin as $type => $struct ) {
            foreach ( $struct as $id => $startVertices )
                    self::addResourceEdges( $startVertices, $type, $id );
        }

        self::unlock();
    }

    //hacemos un mutex para que sólo se pueda acceder sincronamente a estas funciones
    static private function lock()
    {
        if ( self::$locked === 0 ) {

            $time = ini_get( 'max_execution_time' ); //esto me asegura que el script va a morir antes que el bloqueo
            if ( empty( $time ) ) $time = '600';

            $isfree = MyLink::$default->get_rows( 'SELECT GET_LOCK(' . MyLink::quote( DB_DB . '.security.mutex' ) . ',' . $time . ')',
                            true )->getField( 0 );

            while ( !$isfree ) {
                sleep( mt_rand( 1, 2 ) ); //hacemos esperar cada script un tiempo aleatorio pequeño
                //si acaba el tiempo de ejecución antes de que finalize esto se acabará con un rollback.....espero
                $isfree = MyLink::$default->get_rows( 'SELECT GET_LOCK(' . MyLink::quote( DB_DB . '.security.mutex' ) . ',' . $time . ')',
                                true )->getField( 0 );
            }
        }
        self::$locked++;
    }

    static private function unlock()
    {
        self::$locked--;

        if ( self::$locked === 0 ) {
            $sql = 'DO RELEASE_LOCK(' . MyLink::quote( DB_DB . '.security.mutex' ) . ')';
            MyLink::$default->execute( $sql );
        }
    }

}

?>